package com.ilearn.iodata.iolingo.service.processor;


import com.ilearn.iodata.iolingo.service.GetCollectionService;
import com.ilearn.iodata.iolingo.service.processor.core.api.GenericODataPage;
import com.ilearn.iodata.iolingo.service.processor.core.api.GenericODataRequestContextAccess;
import com.ilearn.iodata.iolingo.service.processor.core.api.GenericODataSessionContextAccess;
import com.ilearn.iodata.iolingo.service.processor.core.exception.ODataGenericProcessorException;
import com.ilearn.iodata.iolingo.service.processor.core.processor.GenericAbstractGetRequestProcessor;
import com.ilearn.iodata.iolingo.service.processor.core.query.GenericJoinQuery;
import com.ilearn.iodata.iolingo.service.processor.core.query.Util;
import org.apache.olingo.commons.api.data.*;
import org.apache.olingo.commons.api.edm.EdmEntitySet;
import org.apache.olingo.commons.api.ex.ODataException;
import org.apache.olingo.commons.api.format.ContentType;
import org.apache.olingo.commons.api.http.HttpStatusCode;
import org.apache.olingo.server.api.*;
import org.apache.olingo.server.api.serializer.SerializerResult;
import org.apache.olingo.server.api.uri.UriResource;
import org.apache.olingo.server.api.uri.UriResourceEntitySet;
import org.apache.olingo.server.api.uri.UriResourceKind;
import org.apache.olingo.server.api.uri.queryoption.CountOption;
import org.apache.olingo.server.api.uri.queryoption.SystemQueryOption;
import org.apache.olingo.server.api.uri.queryoption.SystemQueryOptionKind;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;

import static com.ilearn.iodata.iolingo.service.processor.core.exception.ODataGenericProcessorException.MessageKeys.*;

/**
 * Created by lyndon on 2018/6/7.
 */
public class GenericNavigationRequestProcessor extends GenericAbstractGetRequestProcessor {

    private final ServiceMetadata serviceMetadata;
    private final UriResource lastItem;

    private GetCollectionService getCollectionService;

    public GenericNavigationRequestProcessor(final OData odata, final ServiceMetadata serviceMetadata,
                                             GenericODataSessionContextAccess context, 
                                             GenericODataRequestContextAccess requestContext,
                                             GetCollectionService getCollectionService) throws ODataException {
        super(odata, context, requestContext);
        this.serviceMetadata = serviceMetadata;
        final List<UriResource> resourceParts = uriInfo.getUriResourceParts();
        this.lastItem = resourceParts.get(resourceParts.size() - 1);
        this.getCollectionService = getCollectionService;
    }

    @Override
    public void retrieveData(ODataRequest request, ODataResponse response, ContentType responseFormat) throws ODataApplicationException, ODataLibraryException, ODataException {
        final GenericODataPage page = getPage(request);
        // Create a JPQL Query and execute it
        GenericJoinQuery query = null;

        List<UriResource> resourcePaths = uriInfo.getUriResourceParts();

        final UriResource lastItem = resourcePaths.get(resourcePaths.size() - 1);

        // UriResourceKind kind = lastItem.getKind();


        UriResourceEntitySet uriResourceEntitySet = (UriResourceEntitySet) resourcePaths.get(0); // in our example, the first segment is the EntitySet
        EdmEntitySet edmEntitySet = uriResourceEntitySet.getEntitySet();

        try {
            query = new GenericJoinQuery(odata, uriInfo, edmEntitySet , page);
        } catch (ODataException e) {
            throw new ODataGenericProcessorException(QUERY_PREPARATION_ERROR, HttpStatusCode.INTERNAL_SERVER_ERROR, e);
        }

        //final GenericConvertableResult result = query.execute();
        // Read Expand and Collection
        //result.putChildren(readExpandEntities(request.getAllHeaders(), query.getNavigationInfo(), uriInfo));
        // Convert tuple result into an OData Result
        EntityCollection entityCollection = new EntityCollection();
        try {
            query.fetchEntityCollection(getCollectionService, entityCollection);
            //result.asEntityCollection(new GenericTupleChildConverter(sd, odata.createUriHelper(),
                    //serviceMetadata)).get(ROOT_RESULT_KEY);
        } catch (Exception e) {
            throw new ODataGenericProcessorException(QUERY_RESULT_CONV_ERROR, HttpStatusCode.INTERNAL_SERVER_ERROR, e);
        }
        // Set Next Link
        entityCollection.setNext(buildNextLink(page));

        // Count results if requested
        GenericJoinQuery.EntityCount entityCount = query.fetchEntityCount(getCollectionService);

        if (entityCount != null) {
            entityCollection.setCount(entityCount.getCount());
        }

        // 404 Not Found indicates that the resource specified by the request URL does not exist. The response body MAY
        // provide additional information.
        // This is the case for individual property, complex type, a navigation property or entity is not available.
        // See 11.2.6 Requesting Related Entities and 11.2.3 Requesting Individual Properties
        if (isResultEmpty(entityCollection.getEntities()))
            response.setStatusCode(HttpStatusCode.NOT_FOUND.getStatusCode());
            // 200 OK indicates that either a result was found or that the a Entity Collection query had no result
        else if (entityCollection.getEntities() != null) {
            final SerializerResult serializerResult = serializer.serialize(request, entityCollection);
            createSuccessResponce(response, responseFormat, serializerResult);
        } else
            // A request returns 204 No Content if the requested resource has the null value, or if the service applies a
            // return=minimal preference. In this case, the response body MUST be empty.
            response.setStatusCode(HttpStatusCode.NO_CONTENT.getStatusCode());
    }


    private boolean isResultEmpty(List<Entity> entities) throws ODataApplicationException {

        if (entities.isEmpty()
                && lastItem.getKind() == UriResourceKind.entitySet
                && !Util.determineKeyPredicates(lastItem).isEmpty())
            // handle ../Organizations('xx')
            return true;
        else if (lastItem.getKind() == UriResourceKind.primitiveProperty
                || lastItem.getKind() == UriResourceKind.navigationProperty
                || lastItem.getKind() == UriResourceKind.complexProperty) {
            if (entities.isEmpty())
                return true;

            Object resultElement = null;
            String name = "";
            if (lastItem.getKind() == UriResourceKind.primitiveProperty) {
                name = Util.determineStartNavigationPath(uriInfo.getUriResourceParts()).getProperty().getName();
                final Property property = entities.get(0).getProperty(name);
                if (property != null) {
                    resultElement = property.getValue();
                }
            }
            if (lastItem.getKind() == UriResourceKind.complexProperty) {
                name = Util.determineStartNavigationPath(uriInfo.getUriResourceParts()).getProperty().getName();
                final Property property = entities.get(0).getProperty(name);
                if (property != null) {
                    if (property.getValueType() == ValueType.COLLECTION_COMPLEX && property.getValue() != null
                            && !((List<?>) property.getValue()).isEmpty())
                        resultElement = property;
                    else {
                        for (Property p : ((ComplexValue) property.getValue()).getValue()) {
                            if (p.getValue() != null) {
                                resultElement = p;
                                break;
                            }
                        }
                    }
                }
            }
            if (lastItem.getKind() == UriResourceKind.navigationProperty
                    && !entities.get(0).getProperties().isEmpty()) {
                resultElement = Boolean.FALSE;
            }

            return resultElement == null;
        } else
            return false;
    }

    private URI buildNextLink(final GenericODataPage page) throws ODataGenericProcessorException {
        if (page != null && page.getSkiptoken() != null) {
            try {
                return new URI(Util.determineTargetEntitySet(uriInfo.getUriResourceParts()).getName() + "?"
                        + SystemQueryOptionKind.SKIPTOKEN.toString() + "='" + page.getSkiptoken() + "'");
            } catch (URISyntaxException e) {
                throw new ODataGenericProcessorException(ODATA_MAXPAGESIZE_NOT_A_NUMBER, HttpStatusCode.INTERNAL_SERVER_ERROR, e);
            }
        }
        return null;
    }

    private GenericODataPage getPage(final ODataRequest request) throws ODataException {

        GenericODataPage page = new GenericODataPage(uriInfo, 0, Integer.MAX_VALUE, null);
        // Server-Driven-Paging
        if (serverDrivenPaging()) {
            final String skiptoken = skipToken();
            if (skiptoken != null && !skiptoken.isEmpty()) {
                page = sessionContext.getPagingProvider().getNextPage(skiptoken);
                if (page == null)
                    throw new ODataGenericProcessorException(QUERY_SERVER_DRIVEN_PAGING_GONE, HttpStatusCode.GONE, skiptoken);
            } else {
                //final GenericCountQuery countQuery = new GenericJoinQuery(odata, sessionContext, em, request.getAllHeaders(), page);
                //final Integer preferedPagesize = getPreferedPagesize(request);
                //final GenericODataPage firstPage = sessionContext.getPagingProvider().getFristPage(uriInfo, preferedPagesize,
                //        countQuery, em);
                //page = firstPage != null ? firstPage : page;
            }
        }
        return page;
    }

    private boolean serverDrivenPaging() throws ODataGenericProcessorException {

        for (SystemQueryOption option : uriInfo.getSystemQueryOptions()) {
            if (option.getKind() == SystemQueryOptionKind.SKIPTOKEN
                    && sessionContext.getPagingProvider() == null)
                throw new ODataGenericProcessorException(QUERY_SERVER_DRIVEN_PAGING_NOT_IMPLEMENTED,
                        HttpStatusCode.NOT_IMPLEMENTED);
        }
        return sessionContext.getPagingProvider() != null;
    }

    private String skipToken() {
        for (SystemQueryOption option : uriInfo.getSystemQueryOptions()) {
            if (option.getKind() == SystemQueryOptionKind.SKIPTOKEN)
                return option.getText();
        }
        return null;
    }
    
}
